/**
 * AnyStream
 * Copyright (C) 2023 AnyStream Maintainers
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package anystream.ui.media

import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import anystream.models.*
import anystream.models.api.*
import anystream.ui.LocalAnyStreamClient
import anystream.ui.components.PosterCard
import anystream.ui.components.PosterCardWidth
import anystream.ui.generated.resources.Res
import anystream.ui.generated.resources.ic_play
import anystream.ui.generated.resources.tmdb_small
import anystream.ui.util.LocalImageProvider
import coil3.compose.AsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import org.jetbrains.compose.resources.painterResource


@Composable
fun MediaScreen(
    mediaId: String,
    onPlayClick: (mediaLinkId: String) -> Unit,
    onMetadataClick: (metadataId: String) -> Unit,
    onBackClicked: () -> Unit,
    modifier: Modifier = Modifier,
) {
    val client = LocalAnyStreamClient.current
    val mediaResponse by produceState<MediaLookupResponse?>(null, mediaId) {
        value = try {
            client.library.lookupMedia(mediaId)
        } catch (e: Throwable) {
            e.printStackTrace()
            null
        }
    }

    MediaScreen(
        response = mediaResponse,
        onPlayClick = onPlayClick,
        onMetadataClick = onMetadataClick,
        onBackClicked = onBackClicked,
        modifier = modifier,
    )
}

@Composable
fun MediaScreen(
    response: MediaLookupResponse?,
    onPlayClick: (mediaRefId: String) -> Unit,
    onMetadataClick: (metadataId: String) -> Unit,
    onBackClicked: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Column(
        modifier = modifier
            .fillMaxSize()
            .consumeWindowInsets(WindowInsets.statusBars)
            .navigationBarsPadding()
            .verticalScroll(rememberScrollState()),
        verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top)
    ) {
        when (response) {
            is EpisodeResponse -> {
                val mediaItem = remember(response) { response.toMediaItem() }
                BaseDetailsView(
                    mediaItem = mediaItem,
                    onBackClicked = onBackClicked,
                    onPlayClick = onPlayClick,
                )

                CastAndCrewView(response.cast)
            }

            is MovieResponse -> {
                BaseDetailsView(
                    mediaItem = response.toMediaItem(),
                    onBackClicked = onBackClicked,
                    onPlayClick = onPlayClick,
                )

                CastAndCrewView(response.cast)
            }

            is SeasonResponse -> {
                BaseDetailsView(
                    mediaItem = response.toMediaItem(),
                    onBackClicked = onBackClicked,
                    onPlayClick = onPlayClick,
                ) {
                    if (response.episodes.isNotEmpty()) {
                        EpisodeList(
                            episodes = response.episodes,
                            mediaLinks = response.mediaLinkMap,
                            onEpisodeClick = { episode ->
                                onMetadataClick(episode.id)
                            }
                        )
                    }
                }
            }

            is TvShowResponse -> {
                BaseDetailsView(
                    mediaItem = response.toMediaItem(),
                    onBackClicked = onBackClicked,
                    onPlayClick = onPlayClick,
                ) {
                    if (response.seasons.isNotEmpty()) {
                        SeasonRow(
                            seasons = response.seasons,
                            onMetadataClick = onMetadataClick,
                        )
                    }
                }

                CastAndCrewView(response.cast)
            }

            null -> Unit
        }
    }
}

@Composable
private fun BaseDetailsView(
    mediaItem: MediaItem,
    onBackClicked: () -> Unit,
    onPlayClick: (mediaLinkId: String) -> Unit,
    subcontainer: @Composable () -> Unit = {},
) {
    val imageUrlBuilder = LocalImageProvider.current
    Column(
        modifier = Modifier
            .fillMaxWidth()
    ) {
        Box(
            Modifier
                .fillMaxWidth()
                .fillMaxHeight(.37f),
        ) {
            BoxWithConstraints(
                modifier = Modifier
                    .fillMaxWidth()
                //.fillMaxHeight(.7f)
            ) {
                val painter = rememberAsyncImagePainter(
                    model = imageUrlBuilder.url("backdrop", mediaItem.mediaId),
                    contentScale = ContentScale.Crop,
                )
                val state by painter.state.collectAsState()
                when (state) {
                    is AsyncImagePainter.State.Success ->
                        Image(
                            painter = painter,
                            contentDescription = null,
                            modifier = Modifier.fillMaxSize(),
                            contentScale = ContentScale.Crop,
                        )

                    is AsyncImagePainter.State.Loading -> {
                        Box(
                            modifier = Modifier
                                .fillMaxSize()
                                .background(Color.DarkGray),
                        )
                    }

                    AsyncImagePainter.State.Empty -> Unit
                    is AsyncImagePainter.State.Error -> Unit
                }

                Spacer(
                    Modifier
                        .height(230.dp)
                        .fillMaxWidth()
                        .align(Alignment.BottomCenter)
                        .background(
                            brush = Brush.verticalGradient(
                                colors = listOf(
                                    Color(0x00181A20),
                                    Color(0xFF181A20),
                                ),
                            ),
                        ),
                )
            }
            Column(Modifier.fillMaxSize()) {
                Row(
                    Modifier
                        .fillMaxWidth()
                        .padding(WindowInsets.statusBars.asPaddingValues()),
                ) {
                    IconButton(onClick = onBackClicked) {
                        Icon(
                            Icons.AutoMirrored.Filled.ArrowBack,
                            contentDescription = "Return to previous screen",
                            tint = MaterialTheme.colorScheme.onSurface,
                        )
                    }
                    Spacer(modifier = Modifier.weight(1f))
                    IconButton(onClick = { /*TODO*/ }) {
                        Icon(
                            Icons.Default.Search,
                            contentDescription = null,
                            tint = MaterialTheme.colorScheme.onSurface,
                        )
                    }
                }
                Spacer(modifier = Modifier.weight(1f))
                Row(
                    Modifier
                        .fillMaxWidth()
                        .padding(top = 32.dp)
                        .padding(horizontal = 16.dp),
                    verticalAlignment = Alignment.Bottom,
                ) {
                    PosterCard(
                        title = null,
                        mediaId = mediaItem.mediaId,
                        onClick = null,
                        onPlayClick = null,
                    )

                    MediaMetadata(mediaItem)
                }
            }
        }

        Button(
            onClick = {
                mediaItem.playableMediaLink?.run { onPlayClick(id) }
            },
            colors = ButtonDefaults.buttonColors(
                containerColor = Color.Red,
                contentColor = Color.White,
            ),
            modifier = Modifier
                .padding(top = 24.dp, start = 16.dp)
                .width(PosterCardWidth),
            contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
        ) {
            Icon(
                painterResource(Res.drawable.ic_play),
                contentDescription = null,
                modifier = Modifier.size(20.dp),
            )
            Text(
                text = if (mediaItem.playbackState == null) "Play" else "Resume",
                modifier = Modifier.padding(start = 4.dp),
            )
        }

        ExpandableText(mediaItem.overview)

        fun List<CrewCredit>.toUiString(prefix: String) =
            if (isEmpty()) {
                null
            } else {
                joinToString(
                    transform = { it.person.name },
                    separator = if (size > 2) ", " else " & ",
                    prefix = prefix,
                )
            }
        val crewCreditStrings by remember {
            derivedStateOf {
                when (mediaItem.mediaType) {
                    MediaType.MOVIE -> {
                        listOfNotNull(mediaItem.directors.toUiString("Directed by "))
                    }
                    MediaType.TV_SHOW,
                    MediaType.TV_EPISODE,
                    MediaType.TV_SEASON -> {
                        listOfNotNull(
                            mediaItem.directors.toUiString("Directed by "),
                            mediaItem.writers.toUiString("Written by "),
                            mediaItem.creators.toUiString("Created by "),
                        ).take(2)
                    }
                }
            }
        }
        crewCreditStrings.forEach { string ->
            Text(
                text = string,
                color = Color(0x80FFFFFF),
                style = MaterialTheme.typography.bodySmall,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier
                    .padding(horizontal = 16.dp)
            )
        }

        Spacer(Modifier.height(12.dp))

        Box {
            subcontainer()
        }
    }
}

@Composable
private fun CastAndCrewView(
    credits: List<CastCredit>
) {
    val size = 140.dp
    if (credits.isNotEmpty()) {
        val imageUrlBuilder = LocalImageProvider.current

        BaseRow(
            title = "Cast & Crew",
            items = credits,
        ) { credit ->
            Column(
                modifier = Modifier.width(size),
                verticalArrangement = Arrangement.spacedBy(4.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
            ) {
                val painter = rememberAsyncImagePainter(
                    model = imageUrlBuilder.url("people", credit.person.id),
                    contentScale = ContentScale.Crop,
                )
                val state by painter.state.collectAsState()
                when (state) {
                    is AsyncImagePainter.State.Success -> {
                        Image(
                            painter = painter,
                            contentDescription = null,
                            contentScale = ContentScale.Crop,
                            modifier = Modifier
                                .size(size)
                                .clip(CircleShape),
                        )
                    }

                    AsyncImagePainter.State.Empty,
                    is AsyncImagePainter.State.Error,
                    is AsyncImagePainter.State.Loading -> {
                        Box(
                            modifier = Modifier
                                .size(size)
                                .clip(CircleShape)
                                .background(Color.DarkGray),
                            contentAlignment = Alignment.Center,
                        ) {
                            Text(
                                text = buildString {
                                    val nameParts = credit.person.name.split(" ")
                                    append(nameParts.first().first().uppercase())
                                    if (nameParts.size > 1) {
                                        append(nameParts.last().first().uppercase())
                                    }
                                },
                                style = MaterialTheme.typography.headlineLarge,
                                maxLines = 1,
                                overflow = TextOverflow.Clip,
                            )
                        }
                    }

                }

                Spacer(Modifier.size(8.dp))
                Text(
                    text = credit.person.name,
                    overflow = TextOverflow.Ellipsis,
                    maxLines = 1,
                    softWrap = false,
                )
                Text(
                    text = credit.character,
                    overflow = TextOverflow.Ellipsis,
                    maxLines = 1,
                    softWrap = false,
                )
            }
        }
    }
}

@Composable
private fun MediaMetadata(mediaItem: MediaItem) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(start = 16.dp),
        verticalArrangement = Arrangement.Bottom,
    ) {
        Spacer(modifier = Modifier.weight(1f))
        Text(
            text = mediaItem.contentTitle,
            style = MaterialTheme.typography.headlineMedium
        )
        val subtitles by remember {
            derivedStateOf { listOfNotNull(mediaItem.subtitle1, mediaItem.subtitle2) }
        }
        subtitles.forEach { subtitle ->
            Text(
                text = subtitle,
                style = MaterialTheme.typography.headlineSmall,
            )
        }
        Spacer(Modifier.height(4.dp))
        Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
            val items = remember(mediaItem) {
                listOfNotNull(
                    mediaItem.releaseYear,
                    mediaItem.runtime?.asFriendlyString(),
                    mediaItem.contentRating,
                )
            }
            items.forEachIndexed { index, item ->
                if (index > 0) {
                    Text(
                        text = "•",
                        color = Color(0x80FFFFFF),
                        style = MaterialTheme.typography.bodyLarge
                    )
                }
                Text(
                    text = item,
                    color = Color(0x80FFFFFF),
                    style = MaterialTheme.typography.bodyLarge,
                )
            }
        }
        Spacer(Modifier.height(2.dp))
        val tmdbRating = mediaItem.tmdbRating?.toString()
        if (tmdbRating != null) {
            Row(
                horizontalArrangement = Arrangement.spacedBy(4.dp),
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Text(
                    text = "$tmdbRating%",
                    color = Color(0x80FFFFFF),
                    style = MaterialTheme.typography.bodyLarge,
                )

                Image(
                    painter = painterResource(Res.drawable.tmdb_small),
                    contentDescription = null,
                    modifier = Modifier.height(12.dp)
                )
            }
        }
    }
}

@Composable
private fun SeasonRow(
    seasons: List<TvSeason>,
    onMetadataClick: (metadataId: String) -> Unit,
) {
    BaseRow(
        title = "${seasons.size} Seasons",
        items = seasons,
    ) { season ->
        PosterCard(
            title = season.name,
            mediaId = season.id,
            onClick = { onMetadataClick(season.id) },
            onPlayClick = {},
        )
    }
}

@Composable
private fun EpisodeList(
    episodes: List<Episode>,
    mediaLinks: Map<String, MediaLink>,
    onEpisodeClick: (episode: Episode) -> Unit,
) {
    Column(
        modifier = Modifier.fillMaxWidth(),
        verticalArrangement = Arrangement.spacedBy(6.dp),
    ) {
        Text(
            text = "${episodes.size} Episodes",
            style = MaterialTheme.typography.headlineSmall,
            modifier = Modifier
                .padding(horizontal = 16.dp)
        )
        episodes.forEachIndexed { index, episode ->
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .clickable { onEpisodeClick(episode) },
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Box(
                    modifier = Modifier
                        .padding(8.dp)
                        .weight(1f)
                        .aspectRatio(16f / 9f),
                ) {
                    val imageUrlBuilder = LocalImageProvider.current
                    val painter = rememberAsyncImagePainter(
                        model = imageUrlBuilder.url("poster", episode.id, 300),
                        contentScale = ContentScale.FillBounds,
                    )
                    val state by painter.state.collectAsState()
                    val loaded by produceState(state is AsyncImagePainter.State.Success, state) {
                        value = state is AsyncImagePainter.State.Success
                    }
                    androidx.compose.animation.AnimatedVisibility(
                        visible = loaded,
                        enter = fadeIn(),
                        exit = fadeOut(),
                        modifier = Modifier
                            .fillMaxSize()
                            .clip(RoundedCornerShape(6.dp))
                            .shadow(4.dp),
                    ) {
                        Image(
                            painter = painter,
                            contentDescription = null,
                            modifier = Modifier.fillMaxSize(),
                            contentScale = ContentScale.FillBounds,
                        )
                    }
                }

                Column(
                    modifier = Modifier
                        .weight(2f)
                        .padding(12.dp),
                ) {
                    Text(
                        text = episode.name,
                        style = MaterialTheme.typography.labelLarge,
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis,
                        softWrap = false,
                    )

                    // TODO: proper datetime formatter
                    Text(
                        text = episode.airDate
                            ?.toLocalDateTime(TimeZone.currentSystemDefault())
                            ?.let {
                                "${it.month.name.take(1)}${
                                    it.month.name.substring(1, 3).lowercase()
                                } ${it.dayOfMonth}, ${it.year}"
                            }
                            ?: "unaired",
                        style = MaterialTheme.typography.labelSmall,
                        color = Color(0x80FFFFFF),
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis,
                        softWrap = false,
                    )

                    Text(
                        text = episode.overview,
                        style = MaterialTheme.typography.bodySmall,
                        color = Color(0x80FFFFFF),
                        maxLines = 2,
                        overflow = TextOverflow.Ellipsis,
                    )
                }
            }

            if (index < episodes.lastIndex) {
                Spacer(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(horizontal = 12.dp)
                        .height(1.dp)
                        .background(MaterialTheme.colorScheme.onBackground.copy(alpha = 0.2f))
                )
            }
        }
    }
}

@Composable
private fun <T> BaseRow(
    title: String,
    items: List<T>,
    buildItem: @Composable (T) -> Unit,
) {
    Column {
        Text(
            text = title,
            style = MaterialTheme.typography.headlineSmall,
            modifier = Modifier
                .padding(horizontal = 16.dp)
        )
        Spacer(modifier = Modifier.height(6.dp))
        LazyRow(
            horizontalArrangement = Arrangement.spacedBy(12.dp),
        ) {
            item { Spacer(Modifier.width(4.dp)) }
            items(items) {
                buildItem(it)
            }
            item { Spacer(Modifier.width(4.dp)) }
        }
    }
}

@Composable
internal fun ExpandableText(overview: String) {
    var isExpanded by remember { mutableStateOf(false) }

    Column(Modifier) {
        Text(
            text = overview,
            modifier = Modifier
                .padding(16.dp)
                .clickable { isExpanded = !isExpanded }
                .animateContentSize(tween(150)),
            style = MaterialTheme.typography.bodyLarge,
            maxLines = if (isExpanded) Int.MAX_VALUE else 3,
            overflow = TextOverflow.Ellipsis,
        )
    }
}
